﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Management.Deployment;

namespace CorpStoreServer
{
    class StartEnterpriseApp
    {
        private Log log;

        /// <summary>
        /// This function attempts to start an enterprise app.  If the app is not installed yet, 
        /// an attempt to install the package from the appx file defined by the 'appxFn' parameter
        /// will be made.  Note that the machine attempting this installation must contain a sideload
        /// license or be running Win 8 Enterprise edition.  In addition, the 'Allow all trusted 
        /// applications to install' Group Policy setting must be enabled.
        /// 
        /// displayName=<display-name of store app>|familyName=<package family name>
        /// appxFn=<full path to *.appx package (only used for internal sideloaded apps)>
        /// installOnly=true (optional parameter to merely install the app if need be - not run it)
        /// </summary>
        /// <param name="parms"></param>
        public void Run(Log log, NameValueCollection parms)
        {
            try
            {
                string displayName = GenLib.GetParm(parms, "displayName");
                string familyName = GenLib.GetParm(parms, "familyName");
                string appxFn = GenLib.GetParm(parms, "appxFn");
                string installOnly = GenLib.GetParm(parms, "installOnly");
                string packageIdentity = "App";
                string version = "";
                bool blnPackageOK = true;
                string strErr;
                Package package;

                this.log = log;
                logInfo("StartEnterpriseApp started with displayName '{0}', familyName '{1}', appxFn = '{2}', installOnly = '{3}'", displayName, familyName, appxFn, installOnly);

                if (!string.IsNullOrEmpty(familyName))
                    package = GenLib.FindPackageByFamilyName(familyName);
                else
                    package = GenLib.FindPackageByDisplayName(displayName);

                // attempt to get package details
                if (package != null)
                {
                    if (GenLib.GetPackageDetails(package, out packageIdentity, out version, out strErr))
                    {
                        logInfo("Got package details: identity = '{0}', version = '{1}'", packageIdentity, version);
                        blnPackageOK = !string.IsNullOrEmpty(packageIdentity);
                    }
                    else
                        logErr("Error getting package details for package '{0}' : {1}", package.Id.FamilyName, strErr);
                }

                // install package if not found!
                if (package == null || !packageIsCurrent(appxFn, version))
                {
                    logInfo("Package with display name '{0}' or familyName '{1}' could not be found - attempting install from appxFn '{2}'", displayName, familyName, appxFn);
                    if (installPackage(appxFn))
                    {
                        Thread.Sleep(2000); // wait 2 seconds to let installation finish
                        if (!string.IsNullOrEmpty(familyName))
                            package = GenLib.FindPackageByFamilyName(familyName);
                        else
                            package = GenLib.FindPackageByDisplayName(displayName);

                        GenLib.GetPackageDetails(package, out packageIdentity, out version, out strErr);
                        logInfo("Got package details: identity = '{0}', version = '{1}'", packageIdentity, version);
                        blnPackageOK = (!string.IsNullOrEmpty(packageIdentity));

                        // update our master catalog with current createDate for this app
                        updateInstalledInfoInMaster(package.Id.FamilyName, package.InstalledLocation.Path);
                    }
                    else
                        // error installing package
                        blnPackageOK = false;
                }

                // check to see if package already exists on this machine
                if (blnPackageOK && string.IsNullOrEmpty(installOnly))
                {
                    uint processID;
                    string id = package.Id.FamilyName + "!" + packageIdentity;

                    logInfo("About to activate enterprise app '{0}'", id);
                    CorpStoreServer.ActivationManager.ApplicationActivationManager appActiveManager = new ActivationManager.ApplicationActivationManager();
                    appActiveManager.ActivateApplication(id, null, CorpStoreServer.ActivationManager.ActivateOptions.None, out processID);

                    if (processID == 0)
                        logErr("Error starting enterprise app {1} - cannot start", displayName);
                }
            }
            catch (Exception ex)
            {
                logErr("Unhandled exception in startEnterpriseApp: {0}", ex.Message);
            }
        }


        /// <summary>
        /// This function attempts to update the createDate and installedLocation of
        /// masterCatalog.json for the package whose family name is 'pkgFamilyName'.  
        /// </summary>
        /// <param name="pkgFamilyName">package family name to find within masterCatalog.json</param>
        /// <param name="pkgInstallLocation">new install location to save within masterCatalog.json</param>
        /// <returns>true if updated ok</returns>
        private bool updateInstalledInfoInMaster(string pkgFamilyName, string pkgInstallLocation)
        {
            bool blnOK = false;

            try
            {
                // first attempt to find corpStore package so we can know where masterCatalog.json lives
                Package corpPkg = GenLib.FindPackageByFamilyName(Main.STORE_FAMILY_NAME);
                if (corpPkg != null)
                {
                    // found our corpstore app package - now check if masterCatalog.json exists
                    string masterFn = string.Format(@"{0}\Packages\{1}\LocalState\masterCatalog.json",
                                                                    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                                                    corpPkg.Id.FamilyName);
                    if (System.IO.File.Exists(masterFn))
                    {
                        // found master file - read it in
                        string rawData = System.IO.File.ReadAllText(masterFn);
                        PackageInfoGroupList groupList = JsonConvert.DeserializeObject<PackageInfoGroupList>(rawData);
                        logInfo("Found {0} groups in groupList read from {1}; rawData length = {2}", groupList.Groups.Count, masterFn, rawData.Length);

                        // now we have group list from master catalog - attempt to find app corresponding to package sent in to us
                        PackageInfo foundApp = groupList.Groups.SelectMany(g => g.Applications).FirstOrDefault(a => a.FamilyName.Equals(pkgFamilyName, StringComparison.OrdinalIgnoreCase));
                        if (foundApp != null)
                        {
                            // found app to update our create date on - doit
                            logInfo("Found our app with familyName '{0}' in our groupList read from masterCatalog.json OK; updating with installLocation of '{1}", pkgFamilyName, pkgInstallLocation);
                            foundApp.CreateDate = DateTime.Now.ToString();
                            foundApp.InstalledLocation = pkgInstallLocation;

                            // and write back our changes to masterCatalog
                            System.IO.File.WriteAllText(masterFn, JsonConvert.SerializeObject(groupList, Newtonsoft.Json.Formatting.Indented));

                            logInfo("Updated createDate for app '{0}' to be current date since it was just installed", foundApp.DisplayName);
                            blnOK = true;
                        }
                        else
                            logErr("Cannot find app with familyName '{0}' in our group list read from '{1}'", pkgFamilyName, masterFn);
                    }
                    else
                        logErr("Cannot find master catalog at '{0}' - cannot update with createDate of newly installed app", masterFn);
                }
                else
                    logErr("Cannot find corpStore package in our set of installed apps - cannot update our master catalog with newly installed app");

            }
            catch (Exception ex)
            {
                logErr("Unhandled exception inupdateCreateDateInMasterCatalog: {0}", ex.Message);
            }

            return blnOK;
        }


        /// <summary>
        /// This function attempts to install a store app whose appx install file
        /// is located at 'appxFn'.  
        /// </summary>
        /// <param name="appxFn">path+filename to *.appx file</param>
        /// <returns>true if package was installed successfully</returns>
        private bool installPackage(string appxFn)
        {
            bool blnOK = false;

            try
            {
                logInfo("Attempting to install package from appx file '{0}'", appxFn);

                PackageManager mgr = new PackageManager();
                Windows.Foundation.IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deploymentOperation = mgr.AddPackageAsync(new Uri(appxFn), null, Windows.Management.Deployment.DeploymentOptions.None);

                ManualResetEvent opCompletedEvent = new ManualResetEvent(false); // this event will be signaled when the deployment operation has completed.
                deploymentOperation.Completed = (depProgress, status) => { opCompletedEvent.Set(); };

                logInfo("Installing package {0}... waiting to complete", appxFn );
                opCompletedEvent.WaitOne();

                switch (deploymentOperation.Status)
                {
                    case Windows.Foundation.AsyncStatus.Completed:
                        blnOK = true;
                        logInfo("Installation succeeded!");
                        break;

                    case Windows.Foundation.AsyncStatus.Error:
                        Windows.Management.Deployment.DeploymentResult deploymentResult = deploymentOperation.GetResults();
                        logErr("Installation Error: {0}", deploymentOperation.ErrorCode);
                        logErr("Detailed Error Text: {0}", deploymentResult.ErrorText);
                        break;

                    case Windows.Foundation.AsyncStatus.Canceled:
                        logInfo("Installation Canceled");
                        break;

                    default:
                        logInfo("Installation status unknown!");
                        break;
                }
            }
            catch (Exception ex)
            {
                logErr("Unhandled exception in installPackage: {0}", ex.Message);
            }

            return blnOK;
        }

        private bool packageIsCurrent(string appxPath, string version)
        {
            // tbc
            return true;
        }


        private void logInfo(string msg, params object[] parms)
        {
            if (log != null)
                log.LogInfo(msg, parms);
        }


        private void logErr(string msg, params object[] parms)
        {
            if (log != null)
                log.LogErr(msg, parms);
        }


    }
}
